package com.baseproject.datasource.remote;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.test.espresso.IdlingResource;
import android.support.test.espresso.idling.CountingIdlingResource;
import android.text.TextUtils;

import com.baseproject.BaseApplication;
import com.baseproject.appconfig.AppConfigBaseProject;
import com.baseproject.datasource.get.procedure.DataProcedure;
import com.baseproject.json.JsonUtils;
import com.baseproject.util.LogUtils;
import com.baseproject.util.PreferencesUtils;
import com.baseproject.widget.FrameAnimatorProgressDialog;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import okhttp3.ResponseBody;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

/**
 * Implementation of the data source that adds a latency simulating network.
 */
public class RemoteDataSource {

    private static final String TAG = "RemoteDataSource";
    private static RemoteDataSource INSTANCE;
    private static final int SERVICE_LATENCY_IN_MILLIS = 1000;

    private BaseRemoteStrategy baseRemoteStrategy;

    // The Idling Resource which will be null in production.
    @Nullable
    private CountingIdlingResource mIdlingResource;

    /**
     * Only called from test, creates and returns a new {@link CountingIdlingResource}.
     */
    @VisibleForTesting
    @NonNull
    public IdlingResource getIdlingResource() {
        if (mIdlingResource == null) {
            mIdlingResource = new CountingIdlingResource(getClass().getSimpleName(), true);
        }
        return mIdlingResource;
    }

    public static RemoteDataSource getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new RemoteDataSource();
        }
        return INSTANCE;
    }

    // Prevent direct instantiation.
    private RemoteDataSource() {
        baseRemoteStrategy = new RetrofitStrategy();
    }

    public void setBaseRemoteStrategy(BaseRemoteStrategy baseRemoteStrategy) {
        this.baseRemoteStrategy = baseRemoteStrategy;
    }

    public BaseRemoteStrategy getBaseRemoteStrategy() {
        return baseRemoteStrategy;
    }

    private static void showProgressIfNeed(DataProcedure dataProcedure) {
        showProgressIfNeed(dataProcedure.isIfShowProgressDialog());
    }

    private static void showProgressIfNeed(boolean ifShow) {
        if (ifShow) {
            FrameAnimatorProgressDialog.showInCurrentActivity();
        }
    }

    public <T> Observable<List<T>> getList(DataProcedure dataProcedure, final Class<T> className) {
        showProgressIfNeed(dataProcedure);
        if (null != mIdlingResource) {
            mIdlingResource.increment();
        }
        final String uniqueKey =  dataProcedure.getRemoteRequestParams().getUniqueKey();
        final String url = dataProcedure.getRemoteRequestParams().getUrl();
        Map<String, String> queryParams = dataProcedure.getRemoteRequestParams().getQueryParams();
        return baseRemoteStrategy.getList(url, getToken(url, queryParams, "get"), queryParams)
                .map(new Func1<ResponseBody, List<T>>() {
                    @Override
                    public List<T> call(ResponseBody responseBody) {
                        if (null != mIdlingResource && !mIdlingResource.isIdleNow()) {
                            mIdlingResource.decrement();
                        }
                        Object objectData = handleResponseBody(url, responseBody);
                        if (null == objectData) {
                            return null;
                        }
                        List<T> contentList = dataProcedure.getRequestType().getListFromRemoteResponseData(objectData, className);
                        if (null != contentList && contentList.size() > 0 && dataProcedure.isIfNeedSaveToLocal()) {
                            dataProcedure.getRequestType().save(contentList, dataProcedure);
                        }
                        FrameAnimatorProgressDialog._dismiss();//完成的时候取消对话框
                        return contentList;
                    }
                })
                .retryWhen(new RetryWhenProcess())
                .doOnError(new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        FrameAnimatorProgressDialog._dismiss();//完成的时候取消对话框
                    }
                })
                .subscribeOn(dataProcedure.getSubscribeOn())
                .observeOn(dataProcedure.getObserveOn());
    }


    public <T> Observable<String> httpPost(String url, Map<String, String> params) {
        showProgressIfNeed(true);
        return baseRemoteStrategy.httpPost(url, params, getToken(url, params, "post"))
                .map(new Func1<ResponseBody, String>() {
                    @Override
                    public String call(ResponseBody responseBody) {
                        handleResponseBody(url, responseBody);
                        FrameAnimatorProgressDialog._dismiss();//完成的时候取消对话框
                        return null;
                    }
                })
                .doOnError(new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        FrameAnimatorProgressDialog._dismiss();//完成的时候取消对话框
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    public String getToken(String url, Map<String, String> params, String requestType) {
        String token;
        if (null != BaseApplication.getApplication()) {
            token = PreferencesUtils.getString(BaseApplication.getApplication().getApplicationContext(), "token", "");
        } else {
            token = "application is null which cause token is invaild";
        }
        LogUtils.d(requestType + "! url = " + url
                + " params = " + params
                + " token = " + token);
        return token;
    }

    private Object handleResponseBody(String url, ResponseBody responseBody) {
        String responseString;
        try {
            responseString = JsonUtils.getInstance().filterInvaild(responseBody.string());
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("responseString IOException! ");
        }
        throwExceptionIfNeed(responseString);
        JSONObject jsonObject = JsonUtils.strToJSONObject(responseString);
        LogUtils.d("response! url = " + url + " jsonObject = " + jsonObject);
        List<String> serverResponseConfig = AppConfigBaseProject.serverResponseConfig;
        throwExceptionIfNeed(jsonObject, serverResponseConfig);
        Object objectData = jsonObject.opt(serverResponseConfig.get(0));
        return objectData;
    }

    private void appendParamsIfNeed(JSONObject jsonObjectData, HashMap<String, String> appendSaveParams) {
        if (null != appendSaveParams) {
            Set keys = appendSaveParams.keySet();
            Iterator iterator = keys.iterator();
            while (iterator.hasNext()) {
                String key = (String) iterator.next();
                String value = appendSaveParams.get(key);
                if (value != null) {
                    try {
                        jsonObjectData.put(key, value);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void throwExceptionIfNeed(String responseString) throws RuntimeException {
        if (TextUtils.isEmpty(responseString)) {
            throw new RuntimeException("responseString is empty! ");
        }
    }

    private void throwExceptionIfNeed(JSONObject jsonObject, List<String> serverResponseConfig) throws RuntimeException {
        int code = jsonObject.optInt(serverResponseConfig.get(2));
        if (code != Integer.parseInt(serverResponseConfig.get(1))) {
            String message = jsonObject.optString(serverResponseConfig.get(3));
            throw new RuntimeException(code + "," + message);
        }
    }

    public Observable<InputStream> downloadFile(final String url, final String destDir, final String destFileName) {
        showProgressIfNeed(true);
        LogUtils.d("url = " + url);
        return baseRemoteStrategy.downloadFile(url)
                .observeOn(Schedulers.io())
                .map(new Func1<ResponseBody, InputStream>() {
                    @Override
                    public InputStream call(ResponseBody responseBody) {
                        InputStream is = responseBody.byteStream();
                        FrameAnimatorProgressDialog._dismiss();//完成的时候取消对话框
                        return is;
                    }
                })
                .doOnError(new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        FrameAnimatorProgressDialog._dismiss();//完成的时候取消对话框
                    }
                });
    }

}
